Skip to content

fix: optional canvas-confetti via CDN with cached promise (Fix 2)#409

Open
Niboshi-Wasabi wants to merge 1244 commits intokoala73:mainfrom
Niboshi-Wasabi:fix/canvas-confetti-cdn-optional
Open

fix: optional canvas-confetti via CDN with cached promise (Fix 2)#409
Niboshi-Wasabi wants to merge 1244 commits intokoala73:mainfrom
Niboshi-Wasabi:fix/canvas-confetti-cdn-optional

Conversation

@Niboshi-Wasabi
Copy link
Contributor

Summary

Makes the celebration service work when the canvas-confetti package is not installed in node_modules, so the app builds and runs without "Failed to resolve import 'canvas-confetti'" (e.g. after a fresh clone before npm install, or in environments where dependencies are not fully installed).

When the import fails: The error occurs when Vite tries to resolve canvas-confetti at build or dev startup and the package is missing from node_modules — for example right after cloning the repo without running npm install, or in a workspace where dependencies have not been installed yet. We did not observe it in CI, Tauri build, or a specific Node version; it was the "package not installed" case.

Implementation:

  • Load confetti at runtime from a CDN script instead of bundling the npm package. No import 'canvas-confetti' in source, so Vite never tries to resolve it.
  • Use a module-scoped cached promise for the load so that only one <script> tag is ever appended. The record celebration calls run() twice (300ms apart); without the cache, a second script would be appended if the first load had not finished. All callers now share the same promise.
  • Do not add optimizeDeps.include: ['canvas-confetti'] in vite.config, so the package remains optional and Vite does not try to pre-bundle it at dev startup.

Trade-off: If the CDN is unavailable or the user is offline, celebrations still run but no confetti is shown. When the package is installed (after npm install), the CDN is used the same way so behavior is consistent.

Type of change

  • Bug fix
  • New feature
  • New data source / feed
  • New map layer
  • Refactor / code cleanup
  • Documentation
  • CI / Build / Infrastructure

Affected areas

  • Map / Globe
  • News panels / RSS feeds
  • AI Insights / World Brief
  • Market Radar / Crypto
  • Desktop app (Tauri)
  • API endpoints (/api/*)
  • Config / Settings
  • Other: celebration service (src/services/celebration.ts)

Checklist

  • Tested on worldmonitor.app variant
  • Tested on tech.worldmonitor.app variant (if applicable)
  • New RSS feed domains added to api/rss-proxy.js allowlist (if adding feeds)
  • No API keys or secrets committed
  • TypeScript compiles without errors (npm run typecheck)

Screenshots

None. Manual check: run the app without canvas-confetti in node_modules (e.g. remove it from node_modules and run npm run dev); app should start and celebrations should load confetti from CDN when triggered.

koala73 and others added 30 commits February 19, 2026 07:24
- Replace MIT with AGPL-3.0-only to enforce attribution on derivatives
- Move hardcoded Sentry DSN to VITE_SENTRY_DSN env var
- Add null-coalesce guards for i18n legend keys and SVG viewBox
- Extend Sentry ignoreErrors with 7 additional noise patterns
## Summary

Fixes a css transparency issue when selecting languages.

Hi! First time contributing here. Found this app and thought it was
really cool. I realized it was a github repo and thought I'd try and
commit some fixes that I find. If y'all need me to update this PR in any
way let me know.

## Type of change

- [x] Bug fix
- [ ] New feature
- [ ] New data source / feed
- [ ] New map layer
- [ ] Refactor / code cleanup
- [ ] Documentation
- [ ] CI / Build / Infrastructure

## Affected areas

- [ ] Map / Globe
- [ ] News panels / RSS feeds
- [ ] AI Insights / World Brief
- [ ] Market Radar / Crypto
- [ ] Desktop app (Tauri)
- [ ] API endpoints (`/api/*`)
- [ ] Config / Settings
- [x] Other: <!-- Language dropdown-->

## Checklist

- [x] Tested on [worldmonitor.app](https://worldmonitor.app) variant
- [ ] Tested on [tech.worldmonitor.app](https://tech.worldmonitor.app)
variant (if applicable)
- [ ] New RSS feed domains added to `api/rss-proxy.js` allowlist (if
adding feeds)
- [x] No API keys or secrets committed
- [x] TypeScript compiles without errors (`npm run typecheck`)

## Screenshots
Before:

<img width="1470" height="803" alt="Screenshot 2026-02-18 at 7 00 25 PM"
src="https://github.com/user-attachments/assets/f0e2b8ce-58dc-42f0-a73f-76b7a539fecf"
/>

After:

<img width="1470" height="800" alt="Screenshot 2026-02-18 at 6 59 54 PM"
src="https://github.com/user-attachments/assets/a528fc36-45db-40d7-8463-49fb25d07438"
/>
…lick menus

macOS WKWebView shows native Lookup/Translate/Copy menu on right-click,
overriding the custom "Hide Intelligence Findings" context menu.
Prevents default contextmenu on non-input elements in Tauri only.
Closes koala73#114. On ultra-wide monitors, the map floats left (60% width,
65vh) and panels flow in an L-shape: to the right and below the map.
Uses display:contents on panels-grid so individual panels become flow
children, wrapping naturally around the CSS float. No JS changes.
…riants

WORLDMONITOR-28: Twitter in-app browser (iPad/iOS) injects CONFIG variable.
Existing filter used literal apostrophe which may miss Safari's U+2019.
Changed Can't → Can.t to match any apostrophe character.

Closes WORLDMONITOR-28
- Comprehensive README update: live webcams, ultra-wide layout, Linux
  AppImage, theme system, auto-updater, error tracking, responsive
  layout, virtual scrolling, 13 languages, and 8 new roadmap items
- Sentry triage: WORLDMONITOR-28 noise filter broadened for smart quotes
- Ultra-wide layout: CSS float L-shape for 2000px+ screens (koala73#114)
- Version bump: 2.4.0 → 2.4.1
- fixed incorrect spacing on diagram boxes and arrows
- formatted the tables
everything stays same, no visual changes
- WORLDMONITOR-2A: filter AbortError from fetch abort on navigation
- WORLDMONITOR-29: broaden maplibre _layers null crash pattern in beforeSend
- WORLDMONITOR-Q: filter stale dynamic import module errors (post-deploy 404s)
Follow llmstxt.org standard so LLMs can understand the project.
Concise version (~5KB) and extended version (~21KB) with full
data layers, sources, and architecture details.
Add Turkish (tr) as the 14th supported language with full translation
of all 1,134 locale keys, locale loader, tr-TR formatting, and flag
mapping. Update documentation to reflect 14 languages.
Move pattern from beforeSend extension-only check to ignoreErrors array.
All 16 events were iOS Safari with no stack trace — stale cached assets
after deploys, not actionable bugs.
…ions

- Add i18n keys for all 9 strategic posture theater names (Iran Theater,
  Baltic Theater, Taiwan Strait, etc.) across all 14 languages
- Wire StrategicPosturePanel to use t() for theater display names with
  fallback to English if key is missing
- Include webcam region button translations (ALL, MIDEAST, EUROPE, etc.)
  that were missing from deployed locale files

Fixes koala73#121
- CIIPanel: use existing t('components.cii.noSignals') for empty state
- CountryBriefPage: localize level badge and trend indicator labels
- ETFFlowsPanel: localize summary labels, table headers, net direction
- LiveWebcamsPanel: localize region filter tab labels
- MacroSignalsPanel: localize verdict, signal names, and bullish count
- StrategicRiskPanel: localize score levels, trend label, and trend values
Add Ollama as the primary summarization provider for desktop builds,
sitting before Groq/OpenRouter in the fallback chain. This enables
fully local, unlimited LLM inference via Ollama's OpenAI-compatible
endpoint (/v1/chat/completions).

Changes across six layers:
- runtime-config: OLLAMA_API_URL + OLLAMA_MODEL secret keys, aiOllama
  feature toggle (default on), URL validation
- sidecar: allowlist + endpoint probe validation (tries /v1/models
  then /api/tags)
- api/ollama-summarize.js: new handler mirroring Groq/OpenRouter with
  shared Redis cache keys
- summarization.ts: tryOllama() + updated chain order in normal, beta,
  and translation paths (Ollama → Groq → OpenRouter → Browser T5)
- RuntimeConfigPanel: signup URLs + i18n help text for new keys
- desktop-readiness: aiOllama in key-backed features + readiness check

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
…ayers

Three test files covering Ollama integration:

api/ollama-summarize.test.mjs (9 tests):
- Fallback signal when unconfigured, on API error, on empty response
- Success path with correct provider label and response shape
- Model selection via OLLAMA_MODEL env / default fallback
- Network error handling (ECONNREFUSED)
- Translate mode prompt verification

tests/summarization-chain.test.mjs (7 tests):
- Ollama success short-circuits chain (Groq never called)
- Ollama fail → Groq success fallback
- Full fallback when both unconfigured
- Provider label correctness for Ollama and Groq
- Uniform response shape across providers
- Identical fallback signal shapes

src-tauri/sidecar/local-api-server.test.mjs (8 new tests):
- OLLAMA_API_URL and OLLAMA_MODEL accepted via env-update allowlist
- Unknown keys rejected (403)
- Validation via /v1/models probe (reachable mock)
- Validation via /api/tags native fallback
- OLLAMA_MODEL pass-through validation
- Non-http protocol rejection (422)
- Auth-required behavior preserved with token

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
Server-side: extract shared CORS, validation, caching, prompt building,
and response shaping into api/_summarize-handler.js factory. Each
endpoint (Groq, OpenRouter, Ollama) becomes a thin wrapper calling
createSummarizeHandler() with a provider config: credentials, API URL,
model, headers, and provider label.

Client-side: replace three near-identical tryOllama/tryGroq/tryOpenRouter
functions with a single tryApiProvider() driven by an API_PROVIDERS
config array. Add runApiChain() helper that loops the chain with
progress callbacks. Simplify translateText() from three copy-pasted
blocks to a single loop over the same provider array.

- groq-summarize.js: 297 → 30 lines
- openrouter-summarize.js: 295 → 33 lines
- ollama-summarize.js: 289 → 34 lines
- summarization.ts: 336 → 239 lines
- New _summarize-handler.js: 315 lines (shared)
- Net: -566 lines of duplication removed

Adding a new LLM provider now requires only a provider config object
in the endpoint file + one entry in the API_PROVIDERS array.

Tests: 13 new tests for the shared factory (cache key, dedup, handler
creation, fallback, error casing, HTTP methods). All 42 existing tests
pass unchanged.

https://claude.ai/code/session_01AGg9fG6LZ8Y6XhvLszdfeY
- Use for...of entries() instead of index-based loops in summarization.ts
  to satisfy strict noUncheckedIndexedAccess (7 TS18048/TS2345 errors)
- Replace fragile API_PROVIDERS[1] with .find(p => p.name === groq)
- Add OLLAMA_API_URL and OLLAMA_MODEL to SUPPORTED_SECRET_KEYS in main.rs
  so keychain secrets are injected into sidecar on desktop startup
…and Ollama UX

- Split settings window into 3 tabs: LLMs (Ollama/Groq/OpenRouter),
  API Keys (data feeds), and Debug & Logs
- Add featureFilter option to RuntimeConfigPanel for rendering subsets
- Consolidate keychain to single JSON vault entry (1 macOS prompt vs 20)
- Add Ollama model discovery with /api/tags + /v1/models fallback
- Strip <think> reasoning tokens from Ollama responses
- Suppress thinking with think:false in Ollama request body
- Parallel secret verification with 15s global timeout
- Fix manual model input overlapping dropdown (CSS grid-area + hidden-input class)
- Add loading spinners to settings tab panels
- Suppress notification popups when settings window is open
- Filter embed models from Ollama dropdown
- Fix settings window black screen flash with inline dark background
Add local LLM support mentions across feature descriptions, talking
points, screenshot suggestions, and changelog. New dedicated section
for Ollama/LM Studio as feature koala73#11.
DeckGLMap: guard updateLayers/debounce/raf against null maplibreMap,
null out references in destroy() to prevent post-destroy setProps crash.

main.ts: filter contentWindow.postMessage (Facebook WebView),
vertex shader compile (GPU driver), objectStoreNames (iOS background),
Unexpected identifier https (Safari 16), _0x obfuscated vars (extensions),
WKWebView deallocated (Tauri lifecycle).
koala73 and others added 27 commits February 25, 2026 12:44
…ule (koala73#373)

Switch all 4 WTO trade endpoints from manual getCachedJson/setCachedJson
to cachedFetchJson, which coalesces concurrent cache misses into a single
upstream WTO API call. This prevents hammering the WTO API when multiple
requests arrive during a cache miss window.

Also register tradePolicy with the RefreshScheduler at a 10-minute
interval (full/finance variants) — previously it was only fetched once
at startup with no periodic refresh.

https://claude.ai/code/session_01FdvFhpLALL9iJaF8iXMjtu

Co-authored-by: Claude <noreply@anthropic.com>
…la73#376)

wtoFetch() silently returned null on missing key, HTTP errors, and
exceptions — making it impossible to diagnose why trade data shows
"temporarily unavailable". Now logs the specific failure reason.
- Remove "Bound Rate" column from tariffs table — WTO TP_B_0090
  bound rate data unavailable on our subscription tier (always 0.0%)
- Fix restrictions status labels: backend returns high/moderate/low
  but panel expected IN_FORCE/TERMINATED — now matches actual values
- Update all 17 locale files with new tariff level labels
…73#384)

Two bugs:
1. Single shared circuit breaker cached one FRED series response for all 7 series,
   causing identical wrong values across indicators. Now uses per-series breakers.
2. Tab click listeners were destroyed by setContent() debounce. Replaced with
   event delegation on the stable content element.
…oala73#385)

- Suppress "WTO data temporarily unavailable" banner when cached data
  exists on the active tab — only show when truly empty + upstream down
- Add all missing CSS for trade policy panel (228 lines) — cards with
  hover states, color-coded status badges, structured tariff table,
  trade flow metrics layout, source links, and warning banner styling
* fix: use cachedFetchJson for theater posture to prevent Wingbits API stampede

The theater posture handler used manual getCachedJson/setCachedJson which
has no request coalescing. During the window between a cache miss and the
cache write completing (~15s Wingbits timeout), every concurrent request
bypassed the cache and fired its own POST to Wingbits with 9 bounding
boxes — causing ~1500 requests/min.

Switch to cachedFetchJson which deduplicates concurrent in-flight
requests via a shared promise, so only one upstream fetch runs at a time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: migrate all handlers to cachedFetchJson, fix metadata labeling with cachedFetchJsonWithMeta

- Migrate 48 server handlers from manual getCachedJson/setCachedJson to
  cachedFetchJson, eliminating cache stampede vulnerability across the
  entire codebase (concurrent cache misses now coalesce into one fetch)
- Add cachedFetchJsonWithMeta to redis.ts — returns { data, source }
  atomically, fixing TOCTOU race between separate cache check and fetch
- Wire summarize-article.ts and get-usni-fleet-report.ts to use
  cachedFetchJsonWithMeta for correct provider/cached metadata
- Add try/catch to 7 handlers that lost graceful degradation
- Add 4 tests for WithMeta source labeling: cache hit, fresh miss,
  coalesced followers, and TOCTOU regression protection

* chore: trigger PR sync

---------

Co-authored-by: Mikael Sundberg <mikael.sundberg82@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Supply Chain Disruption Intelligence service

Add new supply_chain domain with 3 RPCs:
- GetShippingRates: FRED Baltic Dry Index with spike detection
- GetChokepointStatus: NGA nav warnings + AIS vessel aggregation
  for 6 global chokepoints with composite disruption scoring
- GetCriticalMinerals: HHI concentration analysis for 7 strategic
  minerals from USGS 2024 production data

Frontend: 3-tab SupplyChainPanel with event delegation, sparkline
SVG, escapeHtml XSS hardening, and circuit breakers per RPC.

13 unit tests for pure scoring functions (_scoring.mjs).

* fix: chokepoint upstream detection, FRED weekly frequency, missing CSS

P1: Chokepoint handler propagates upstreamUnavailable when maritime
sources fail or return empty, instead of always reporting false.

P1: FRED BDI uses frequency=w for true 52-week window.

P2: Add CSS for sc-status-dot, sc-dot-*, sc-risk-* classes.

* fix: allow partial chokepoint data when one upstream source succeeds
…koala73#375)

* feat: dynamic sidecar port with EADDRINUSE fallback

Rust probes port 46123 via TcpListener::bind; if busy, binds port 0 for
an OS-assigned ephemeral port. The actual port is stored in LocalApiState,
passed to sidecar via LOCAL_API_PORT env, and exposed to frontend via
get_local_api_port IPC command.

Frontend resolves the port lazily on first API call (with retry-on-failure
semantics) and caches it. All hardcoded 46123 references replaced with
dynamic getApiBaseUrl()/getLocalApiPort() accessors. CSP connect-src
broadened to http://127.0.0.1:* (frame-src unchanged).

* fix: scope CSP to desktop builds and eliminate port TOCTOU race

P1: Remove http://127.0.0.1:* from index.html (web build CSP). The
wildcard allowed web app JS to probe arbitrary localhost services.
Vite's htmlVariantPlugin now injects localhost CSP only when
VITE_DESKTOP_RUNTIME=1 (desktop builds).

P2: Replace Rust probe_available_port() (bind→release→spawn race)
with a confirmed port handshake. Sidecar now handles EADDRINUSE
fallback internally and writes the actual bound port to a file.
Rust polls the port file (up to 5s) to store only the confirmed port.

* fix: isSafeUrl ReferenceError — addresses scoped inside try block

`let addresses = []` was declared inside the outer `try` block but
referenced after the `catch` on line 200. `let` is block-scoped so
every request through isSafeUrl crashed with:
  ReferenceError: addresses is not defined

Move the declaration before the `try` so it's in scope for the return.
…sabled (koala73#390)

mlWorker.init() was called unconditionally in App.init(), ignoring the
browserModel setting. This caused ONNX model downloads, 120s embed
timeouts, and clustering warnings even with the toggle OFF.

- Gate mlWorker.init() on browserModel setting (or desktop runtime)
- Subscribe to setting changes for dynamic init/terminate on toggle
- Replace mlWorker.init() in sentiment-gate with isAvailable check
- Clean up subscription in App.destroy() to prevent HMR listener leaks
…ature gate (koala73#391)

- Replace non-existent BDIY (Baltic Dry Index) FRED series with real
  public series: PCU483111483111 (Deep Sea Freight PPI) and TSIFRGHT
  (Freight Transportation Index). BDIY is proprietary Baltic Exchange
  data not available on FRED — all shipping requests were failing.
- Make "upstream unavailable" banner per-tab instead of global — shipping
  failure no longer shows the warning on chokepoints and minerals tabs
  that have valid data.
- Remove inconsistent isFeatureAvailable('supplyChain') gate from
  frontend fetchShippingRates() — server handler already returns empty
  when FRED_API_KEY is missing, and chokepoints/minerals had no gate.
… filters (koala73#393)

PR koala73#382 accidentally removed the maplibregl.FilterSpecification type
cast, causing tsc to infer (string | string[])[] which doesn't satisfy
setFilter's parameter type. This broke the Vercel build.
…#392)

- Add to tech variant security feed group
- Add to main variant global feeds as type: 'cyber'
- Add www.ransomware.live to RSS proxy allowlist
- Set source tier to 3 (specialty)
Runs npm run typecheck (tsc --noEmit) on every PR to catch type
errors before merge. Would have prevented the koala73#382 regression.
…73#395)

Linux AppImage (koala73#370, koala73#257):
- Upgrade CI from Ubuntu 22.04 to 24.04 (GLib 2.80 fixes g_task_set_static_name symbol mismatch)
- Set GDK_BACKEND=wayland,x11 when WAYLAND_DISPLAY detected (fixes GTK init on niri/river)
- Disable WebKit bubblewrap sandbox in AppImage context (FUSE mount breaks it → blank screen)
- Set GTK_IM_MODULE to built-in simple context in AppImage (prevents host module conflicts)

RSS proxy:
- Add 32 missing domains to allowlist (EuroNews lang variants, international news feeds)
- Normalize www prefix in domain validation (prevents entire class of www/non-www mismatch 403s)

Console cleanup:
- TrendingKeywords: console.log → console.debug for suppressed terms (30+ noisy log lines)
- PostHog: add ui_host for reverse proxy setup (fixes /ingest/ 404s)
…73#396)

Add a "Streaming" section to the GENERAL settings tab with a quality
dropdown (Auto / Low 360p / Medium 480p / High / HD 720p). The setting
persists to localStorage and applies to all live streams:

- LiveWebcamsPanel: appends vq= to direct embeds, passes through proxy
- LiveNewsPanel: sets quality via YT.Player API onReady + desktop proxy
- YouTube embed proxy: accepts vq param, calls setPlaybackQuality()

Closes koala73#365
…koala73#400)

* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns
…ala73#399)

* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* fix: sort supply chain chokepoints by disruption score descending
* fix: sort tariff datapoints newest-first in trade policy panel

* fix: update tests broken by cachedFetchJson migration

- Restore "Strip unterminated" comment in summarize-article.ts that
  tests use to locate the unterminated tag stripping section
- Update ACLED tests to check for cachedFetchJson instead of removed
  getCachedJson/setCachedJson patterns

* chore: bump version to 2.5.9 and make pre-push hook executable

* docs: update README with supply chain intel, universal CII, Happy Monitor, security hardening, and recent features
…oise filters (koala73#402)

- CSS.escape() on data-news-id querySelector to prevent SyntaxError
  when news item IDs contain special characters (WORLDMONITOR-5J)
- typeof guard on this.player.destroy() for partially-initialized
  YouTube players (WORLDMONITOR-5C/5B)
- 11 new ignoreErrors patterns for IndexedDB races, browser vendor
  internals, and extension-injected errors
- beforeSend filter for blob: URL extension frames
…7 locales (koala73#403)

The header.settings i18n key was incorrectly set to "PANELS" (and its
translations) for the settings modal title. The modal contains General,
Panels, and Sources tabs — the overall title should be "SETTINGS".
…3#404)

* fix: sequential Yahoo calls, sector fallback, and missing try-catch guards

- list-commodity-quotes: replace Promise.all with fetchYahooQuotesBatch to prevent Yahoo 429
- get-sector-summary: add Yahoo Finance fallback when FINNHUB_API_KEY is missing
- list-etf-flows: sequential fetch loop + add missing try-catch around cachedFetchJson
- get-macro-signals: replace unnecessary Promise.allSettled([single]) with direct await

* fix: tighten AI summary prompts to 2 sentences / 60 words max

Summaries were often verbose walls of text despite "2-3 sentences" in
the prompt. Changed to "2 concise sentences MAX (under 60 words total)"
across all brief, analysis, and fallback prompts. Reduced max_tokens
from 150 to 100 to hard-cap output length.
…uards (koala73#406)

- list-commodity-quotes: replace Promise.all with fetchYahooQuotesBatch to prevent Yahoo 429
- get-sector-summary: add Yahoo Finance fallback when FINNHUB_API_KEY is missing
- list-etf-flows: sequential fetch loop + add missing try-catch around cachedFetchJson
- get-macro-signals: replace unnecessary Promise.allSettled([single]) with direct await
…sistence (koala73#407)

- Show rate-limited message instead of generic "Failed to load" on Markets,
  ETF, Commodities, and Sector panels when Yahoo returns 429
- fetchYahooQuotesBatch returns rateLimited flag; early-exit after 3 misses
- ETF panel skips retry loop when rate-limited, shows specific i18n message
- Fallback Finnhub symbols through Yahoo when API key missing
- 401-retry in runtime fetch patch for stale sidecar token after restart
- diagFetch auth helper for settings window diagnostic endpoints
- Verbose toggle writes to writable dataDir instead of read-only app bundle
* chore: bump v2.5.10 and update README for recent fixes

Version 2.5.9 → 2.5.10. Roadmap entries for:
- Yahoo Finance rate-limit UX across all market panels
- Sidecar auth resilience (401-retry, settings diagFetch)
- Verbose toggle persistence to writable data directory
- Finnhub-to-Yahoo fallback routing

* chore: add v2.5.10 changelog entry
- Load confetti from CDN at runtime when package not in node_modules
- Module-scoped cached promise so only one script tag is appended (record type calls run() twice 300ms apart)
- No optimizeDeps for canvas-confetti; package stays optional
- PR rationale and description in docs/PR_FIX2_CANVAS_CONFETTI_BODY.md

Made-with: Cursor
@vercel
Copy link

vercel bot commented Feb 26, 2026

@Niboshi-Wasabi is attempting to deploy a commit to the Elie Team on Vercel.

A member of the Team first needs to authorize it.

@Niboshi-Wasabi
Copy link
Contributor Author

Hi @koala73,

This is the separate PR for Fix 2 we discussed on PR #388: optional canvas-confetti via CDN with a module-scoped cached promise (so only one script tag is appended), no optimizeDeps, and the rationale in the description. When you have time, I’d be glad if you could take a look. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.